﻿using System;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;

namespace NSDuctTape
{
	internal static class MethodImplementations
	{
		private static uint Count(IntPtr instance, IntPtr selector)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
				return (uint)list.Count;

			return (uint)(int)target.GetType().GetInterface("System.Collections.Generic.ICollection`1").GetProperty("Count").GetValue(target, null);
		}

		private static IntPtr ObjectAtIndex(IntPtr instance, IntPtr selector, uint index)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
				return InstanceUtility.InstanceForObject(list[(int)index]);

			return InstanceUtility.InstanceForObject(target.GetType().GetInterface("System.Collections.Generic.IList`1").GetProperty("Item").GetValue(target, new object[] { (int)index }));
		}

		private static void InsertObjectAtIndex(IntPtr instance, IntPtr selector, IntPtr item, uint index)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
				list.Insert((int)index, InstanceUtility.ObjectForInstance(item));
			else
				target.GetType().GetInterface("System.Collections.Generic.IList`1").GetMethod("Insert").Invoke(target, new object[] { (int)index,
					InstanceUtility.ObjectForInstance(item) });
		}

		private static void AddObject(IntPtr instance, IntPtr selector, IntPtr item)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
				list.Add(InstanceUtility.ObjectForInstance(item));
			else
				target.GetType().GetInterface("System.Collections.Generic.ICollection`1").GetMethod("Add").Invoke(target, new object[] { InstanceUtility.ObjectForInstance(item) });
		}

		private static void RemoveObjectAtIndex(IntPtr instance, IntPtr selector, uint index)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
			{
				list.RemoveAt((int)index);
			}
			else
			{
				target.GetType().GetInterface("System.Collections.Generic.IList`1").GetMethod("RemoveAt").Invoke(target, new object[] { (int)index });
			}
		}

		private static void RemoveLastObject(IntPtr instance, IntPtr selector)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
			{
				int last = list.Count - 1;
				list.RemoveAt(last);
			}
			else
			{
				int last = (int)target.GetType().GetInterface("System.Collections.Generic.ICollection`1").GetProperty("Count").GetValue(target, null);
				target.GetType().GetInterface("System.Collections.Generic.IList`1").GetMethod("RemoveAt").Invoke(target, new object[] { last });
			}
		}

		private static void ReplaceObjectAtIndexWithObject(IntPtr instance, IntPtr selector, uint index, IntPtr item)
		{
			object target = InstanceUtility.ObjectForInstance(instance);
			IList list = target as IList;
			if (list != null)
			{
				list[(int)index] = InstanceUtility.ObjectForInstance(item);
			}
			else
			{
				target.GetType().GetInterface("System.Collections.Generic.IList`1").GetProperty("Item").SetValue(target, InstanceUtility.ObjectForInstance(item),
					new object[] { (int)index });
			}
		}

		private static void ForwardInvocation(IntPtr instance, IntPtr selector, IntPtr nativeInvocation)
		{
			Type type = Registrar.TypeForInstance(instance);
			Invocation invocation = new Invocation(nativeInvocation);
			string targetSelector = invocation.Selector;

			MethodBase method = Registrar.MethodForSelector(type, targetSelector);
			if (method is ConstructorInfo)
			{
				InstanceUtility.SendMessageToBaseClass(instance, Selectors.Init);

				object[] arguments = invocation.GetArguments(method.GetParameters());

				object result = (method as ConstructorInfo).Invoke(arguments);
				InstanceUtility.SetObjectForInstance(instance, result);

				invocation.SetReturnValue(instance);
			}
			else
			{
				object target = InstanceUtility.ObjectForInstance(instance);
				object[] arguments = invocation.GetArguments(method.GetParameters());

				object result = method.Invoke(target, arguments);
				invocation.SetReturnValue(result);
			}
		}

		private static IntPtr MethodSignatureForSelector(IntPtr instance, IntPtr selector, IntPtr targetSelector)
		{
			Type type = Registrar.TypeForInstance(instance);
			string strSelector = Marshal.PtrToStringAnsi(targetSelector);
			MethodBase method = Registrar.MethodForSelector(type, strSelector);

			string types = method is MethodInfo ?
				TypeStrings.ForType((method as MethodInfo).ReturnType) :
				TypeStrings.ForType(type); // If the method isn't a MethodInfo, it's a constructor

			// All instance methods take an instance followed by a selector
			types += "@:";

			foreach (ParameterInfo info in method.GetParameters())
				types += TypeStrings.ForType(info.ParameterType);

			using (HGlobal buffer = HGlobal.StringToHGlobalAnsi(types))
				return ObjectiveCMethods.SendMessage(Classes.MethodSignature, Selectors.SignatureWithObjCTypes, buffer.ToIntPtr());
		}

		private static IntPtr ValueForKey(IntPtr instance, IntPtr selector, IntPtr nativeKey)
		{
			string key = NativeString.NativeStringToString(nativeKey);
			if (key == "self")
				return instance;

			Type type = Registrar.TypeForInstance(instance);

			PropertyInfo info = Registrar.PropertyForKey(type, key);
			if (info == null || !info.CanRead)
				return ObjectiveCMethods.SendMessage(instance, Selectors.ValueForUndefinedKey, nativeKey);

			object target = InstanceUtility.ObjectForInstance(instance);
			object result = info.GetValue(target, null);

			Type resultType = info.PropertyType;
			IntPtr nativeResult;
			if (resultType == typeof(bool))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithBool, (bool)result);
			else if (resultType == typeof(byte))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithUnsignedChar, (byte)result);
			else if (resultType == typeof(short))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithShort, (short)result);
			else if (resultType == typeof(int))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithInt, (int)result);
			else if (resultType == typeof(long))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithLong, (long)result);
			else if (resultType == typeof(float))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithFloat, (float)result);
			else if (resultType == typeof(double))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithDouble, (double)result);
			else if (resultType == typeof(ushort))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithUnsignedShort, (ushort)result);
			else if (resultType == typeof(uint))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithUnsignedInt, (uint)result);
			else if (resultType == typeof(ulong))
				nativeResult = ObjectiveCMethods.SendMessage(Classes.Number, Selectors.NumberWithUnsignedLong, (ulong)result);
			else if (resultType == typeof(IntPtr))
				nativeResult = (IntPtr)result;
			else if (resultType == typeof(string))
				nativeResult = NativeString.StringToNativeString(result as string);
			else if (resultType == typeof(Color))
				nativeResult = NativeColor.ColorToNativeColor((Color)result);
			else
				nativeResult = InstanceUtility.InstanceForObject(result);

			return nativeResult;
		}

		private static IntPtr MutableArrayValueForKey(IntPtr instance, IntPtr selector, IntPtr nativeKey)
		{
			string key = NativeString.NativeStringToString(nativeKey);

			Type type = Registrar.TypeForInstance(instance);

			PropertyInfo info = Registrar.PropertyForKey(type, key);
			if (info == null || !info.CanRead ||
				(info.PropertyType.GetInterface("System.Collections.Generic.IList`1") ?? info.PropertyType.GetInterface("System.Collections.IList")) == null)
			{
				ObjectiveCMethods.SendMessage(Classes.Exception, Selectors.RaiseFormat,
					NativeString.StringToNativeString("NSUndefinedKeyException"), IntPtr.Zero);
			}
			else
			{
				object target = InstanceUtility.ObjectForInstance(instance);
				object result = info.GetValue(target, null);

				IntPtr nativeResult = InstanceUtility.InstanceForObject(result);
				return nativeResult;
			}

			return IntPtr.Zero;
		}

		private static void SetValueForKey(IntPtr instance, IntPtr selector, IntPtr nativeValue, IntPtr nativeKey)
		{
			Type type = Registrar.TypeForInstance(instance);

			string key = NativeString.NativeStringToString(nativeKey);

			PropertyInfo info = Registrar.PropertyForKey(type, key);
			if (info == null || !info.CanWrite)
			{
				ObjectiveCMethods.SendMessage(instance, Selectors.SetValueForUndefinedKey, nativeValue, nativeKey);
			}
			else
			{
				object value;
				Type valueType = info.PropertyType;
				if (valueType == typeof(bool))
					value = ObjectiveCMethods.SendMessageWithBoolResult(nativeValue, Selectors.BoolValue);
				else if (valueType == typeof(byte))
					value = ObjectiveCMethods.SendMessageWithByteResult(nativeValue, Selectors.UnsignedCharValue);
				else if (valueType == typeof(short))
					value = ObjectiveCMethods.SendMessageWithInt16Result(nativeValue, Selectors.ShortValue);
				else if (valueType == typeof(int))
					value = ObjectiveCMethods.SendMessageWithInt32Result(nativeValue, Selectors.IntValue);
				else if (valueType == typeof(long))
					value = ObjectiveCMethods.SendMessageWithInt64Result(nativeValue, Selectors.LongValue);
				else if (valueType == typeof(float))
					value = ObjectiveCMethods.SendMessageWithSingleResult(nativeValue, Selectors.FloatValue);
				else if (valueType == typeof(double))
					value = ObjectiveCMethods.SendMessageWithDoubleResult(nativeValue, Selectors.DoubleValue);
				else if (valueType == typeof(ushort))
					value = ObjectiveCMethods.SendMessageWithUInt16Result(nativeValue, Selectors.UnsignedShortValue);
				else if (valueType == typeof(uint))
					value = ObjectiveCMethods.SendMessageWithUInt32Result(nativeValue, Selectors.UnsignedIntValue);
				else if (valueType == typeof(ulong))
					value = ObjectiveCMethods.SendMessageWithUInt64Result(nativeValue, Selectors.UnsignedLongValue);
				else if (valueType == typeof(IntPtr))
					value = nativeValue;
				else if (valueType == typeof(string))
					value = NativeString.NativeStringToString(nativeValue);
				else if (valueType == typeof(Color))
					value = NativeColor.NativeColorToColor(nativeValue);
				else
					value = InstanceUtility.ObjectForInstance(nativeValue);

				info.SetValue(InstanceUtility.ObjectForInstance(instance), value, null);
			}
		}

		private static IntPtr Init(IntPtr instance, IntPtr selector)
		{
			InstanceUtility.SendMessageToBaseClass(instance, Selectors.Init);

			Type type = Registrar.TypeForInstance(instance);
			object result = type.GetConstructor(Type.EmptyTypes).Invoke(null);
			InstanceUtility.SetObjectForInstance(instance, result);

			return instance;
		}

		private static void Dealloc(IntPtr instance, IntPtr selector)
		{
			IntPtr targetHandle;
			ObjectiveCMethods.GetField(instance, "gcHandle", out targetHandle);
			GCHandle.FromIntPtr(targetHandle).Free();

			IntPtr notifyHandle;
			ObjectiveCMethods.GetField(instance, "notifyHandle", out notifyHandle);
			if (notifyHandle != IntPtr.Zero)
			{
				GCHandle handle = GCHandle.FromIntPtr(notifyHandle);
				(handle.Target as IDisposable).Dispose();
				handle.Free();
			}

			InstanceUtility.SendMessageToBaseClass(instance, selector);
		}

		private static bool RespondsToSelector(IntPtr instance, IntPtr selector, IntPtr aSelector)
		{
			if (aSelector == IntPtr.Zero)
				return false;

			Type type = Registrar.TypeForInstance(instance);
			string strSelector = Marshal.PtrToStringAnsi(aSelector);
			return Registrar.MethodForSelector(type, strSelector) != null;
		}
	}
}
